#include "constants.h"
#include "receiver.h"

using namespace std;

int sockfd;
struct addrinfo hints, *servinfo, *p;
int rv;
struct sockaddr_storage their_addr;
bool randomAcks = false;
//uchar data[MAX_PACKET_SIZE];	//Buffer for receiving packet. 4 bytes for sequence number, 2 bytes for checksum, rest for data.
socklen_t addr_len;
fstream fout;
//uchar s[INET6_ADDRSTRLEN];
uint expectedSequenceNumber, writeSeqNum = 0, RWS, windowIndex = 0, windowBase = 0, maxSequenceNumber;
priority_queue<uint,deque<uint,allocator<uint> >,greater<uint> > acksLost;
sem_t writeSemaphore, arrivalSemaphore, printSemaphore, receiveSemaphore;
typedef struct {
	uint sequenceNumber;
	uchar* data;
	bool write;
	bool received;
	int numBytes;
} packetInfo;
packetInfo* window;
bool suppressOutput;


void init(){
	bindSocket();
	sem_init(&writeSemaphore,0,1);
	sem_init(&arrivalSemaphore,0,1);
	sem_init(&printSemaphore,0,1);
	expectedSequenceNumber = 0;
	//Create 10 threads for receiving data
	pthread_t threads[NUM_THREADS];
	threadData thrData[NUM_THREADS];
	for(int i = 0; i < NUM_THREADS; i++){
		thrData[i].threadID = i;
		if(pthread_create(&threads[i], NULL, receive, (void*)&thrData[i]) != 0){
			perror("Thread creation");
		}
	}
	for(int j = 0;j<NUM_THREADS;j++){
		pthread_join(threads[j], NULL);
	}
}

void initializeWindow(){
	window = new packetInfo[RWS];
	for(uint i = 0; i < RWS; i++){
		window[i].sequenceNumber = i;
		window[i].data = NULL;
		window[i].write = false;
		window[i].received = false;
		window[i].numBytes = 0;
	}
}

void printWindow(){
	if(!suppressOutput){
		cout << "WINDOW: [";
		for(uint i = windowIndex; i < (RWS + windowIndex); i++){
			cout << (window[i % RWS].sequenceNumber%maxSequenceNumber) << " (";
			if(window[i % RWS].received){
				cout << "x";
			} else {
				cout << " ";
			}
			cout << "), ";
		}
		cout << "]" << endl;
		cout<<endl;
	}
}

void* receive(void *ptr){
	uchar* data;
	bool loseCurAck = false;
	//int id = threadID;
	int numbytes;
	addr_len = sizeof their_addr;
	uint sequenceNumber;
	initializeWindow();
	while(1){
		data = (uchar*)malloc(MAX_PACKET_SIZE);
		if ((numbytes = recvfrom(sockfd, data, MAX_PACKET_SIZE, 0,
			(struct sockaddr *)&their_addr, &addr_len)) == -1){
				perror("recvfrom");
				free(data);
				return (void*)1;
		} else {
			sem_wait(&arrivalSemaphore);
			sequenceNumber = getSeqNum(data);
			if(isPING(data)){
				if(!suppressOutput){
					cout << "Received PING. Responded." << endl;
				}
				sendPING(data);
			} else {
				if(!suppressOutput){
					cout << "Packet " << (sequenceNumber%maxSequenceNumber) << " received (" << numbytes << " bytes)" << endl;
				}
				if(cksum(data,numbytes)){
					bool accept;
					if(RWS > 1){	//Selective repeat.
						if(sequenceNumber < window[windowIndex].sequenceNumber){
							//No buffer, just ack.
							uchar* ack = (uchar*)malloc(5);	//Acknowledgement packet. 4 bytes for sequence number, 1 byte for ack or negative ack.
							sendAck(ack, sequenceNumber);
							if(!suppressOutput){
								cout << "Sent ACK for packet " << (sequenceNumber%maxSequenceNumber) << "." << endl;
							}
							free(ack);
							accept = false;
						} else {
							for(uint i = windowIndex; i < windowIndex + RWS; i++){
								if(sequenceNumber == window[i % RWS].sequenceNumber){
									accept = true;
									if(window[i % RWS].received){
										//We already received this, ACK was lost probably.
										if(!suppressOutput){
											cout << "Packet " << (sequenceNumber%maxSequenceNumber) << " already received." << endl;
										}
										free(data);
									} else {
										window[i % RWS].data = data;
										window[i % RWS].received = true;
										window[i % RWS].numBytes = numbytes;
									}
									break;
								} else {
									accept = false;
								}
							}
						}
					} else {	//Stop-and-wait, GBN
						if(sequenceNumber == expectedSequenceNumber){
							window[windowIndex].data = data;
							window[windowIndex].received = true;
							window[windowIndex].numBytes = numbytes;
							accept = true;
						} else if(sequenceNumber < expectedSequenceNumber){
							uchar* ack = (uchar*)malloc(5);	//Acknowledgement packet. 4 bytes for sequence number, 1 byte for ack or negative ack.
							sendAck(ack, sequenceNumber);
							if(!suppressOutput){
								cout << "Sent ACK for packet " << (sequenceNumber%maxSequenceNumber) << "." << endl;
							}
							free(ack);
							accept = false;
						} else {
							accept = false;
						}
					}
					if(accept){
						//if checksum succeeded, write bytes to file and send ack
						int random_integer=0;
						int range =100;
						if (randomAcks ==true){
						//	int random_integer = int(rand())%100;
							random_integer = int(rand())%range;
							cout<<"random_integer"<< random_integer<<endl;

							if (random_integer%100<=10){
								loseCurAck=true;
							}
						}
						if((acksLost.top() == sequenceNumber && !acksLost.empty())||(loseCurAck==true)){
							if(!suppressOutput){
								cout << "Losing ACK." << endl;
							}
							if (loseCurAck==false){
								acksLost.pop();
								cout << "Top: " << acksLost.top() << endl;
							}else{
								loseCurAck=false;
							}
						} else {
							uchar* ack = (uchar*)malloc(5);	//Acknowledgement packet. 4 bytes for sequence number, 1 byte for ack or negative ack.
							sendAck(ack, sequenceNumber);
							if(!suppressOutput){
								cout << "Sent ACK for packet " << (sequenceNumber%maxSequenceNumber) << "." << endl;
							}
							free(ack);
						}
						if(sequenceNumber == window[windowIndex].sequenceNumber){
							if(processPacket(data, numbytes) > 0){
								//sending is finished.
								cleanup();
							} else {
								window[windowIndex].sequenceNumber = window[windowIndex].sequenceNumber + RWS;
								window[windowIndex].received = false;
								window[windowIndex].write = false;
								window[windowIndex].data = NULL;
								windowIndex = (windowIndex + 1) % RWS;
								expectedSequenceNumber = sequenceNumber + 1;
								for(uint i = 0; i < RWS; i++){
									if(window[windowIndex].received){
										if(processPacket(window[windowIndex].data, window[windowIndex].numBytes) > 0){
											cleanup();
										} else {
											window[windowIndex].sequenceNumber = window[windowIndex].sequenceNumber + RWS;
											window[windowIndex].received = false;
											window[windowIndex].write = false;
											window[windowIndex].data = NULL;
											windowIndex = (windowIndex + 1) % RWS;
										}
									} else {
										break;
									}
								}
							}
						}
					} else {
						sem_wait(&printSemaphore);
						free(data);
						if(RWS > 1){
							if(!suppressOutput){
								cout << "Packet " << (sequenceNumber%maxSequenceNumber) << " outside of window. Not buffered." << endl;
							}
						} else {
							if(!suppressOutput){
								cout << "Packet " << (sequenceNumber%maxSequenceNumber) << " out of order. Expected " << (expectedSequenceNumber%maxSequenceNumber) << ". Packet discarded." << endl;
							}
						}
						sem_post(&printSemaphore);
					}
				} else {
					free(data);
				}
			}
		}
		printWindow();
		sem_post(&arrivalSemaphore);
	}
	return (void*)0;
}

int processPacket(uchar* data, int numbytes){
	sem_wait(&writeSemaphore);
	int returnVal = 0;
	writeSeqNum++;
	if(isBOF(data)){	//We have a new sequence of packets. New file.
		int fileNameLength = numbytes-HEADER_SIZE;
		char fileName[fileNameLength];
		for(int j = (int)HEADER_SIZE; j < numbytes; j++){
			fileName[j-HEADER_SIZE] = data[j];
		}
		fileName[fileNameLength] = '\0';
		string tempFileName(fileName);
		//system("mkdir /tmp/ismajors");
		tempFileName = tempFileName + ".recvd2";
		cout << "Creating new file: " << tempFileName << endl;
		fout.open(tempFileName.c_str(), ios::out | ios::binary);
		//cout << "Packet " << getSeqNum(data) << " written." << endl;
		free(data);
	} else {
		int wrote = 0;
		for(int j = (int)HEADER_SIZE; j < numbytes; j++){
			if(fout.good()) {
				fout.put(data[j]);
				wrote++;
			}
		}
		if(isEOF(data)){
			fout.close();
			returnVal = 1;
		}
		//cout << "Packet " << getSeqNum(data) << " written." << endl;
		free(data);
	}
	sem_post(&writeSemaphore);
	return returnVal;
}

void cleanup(){
	cout << "File transferred." << endl;
	expectedSequenceNumber = 0;
	windowIndex = 0;
	delete window;
	initializeWindow();
}


/**
  * cksum - ushort
  *		A method to determine what the checksum is for a given message,
  *		and return the value.
  *	uchar* addr - the data to have the checksum calculated on.
  * int count   - The total 
  */
ushort cksum(uchar* addr, int count) {
	/* Compute Internet Checksum for "count" bytes
	*         beginning at location "addr".
	*/
	register long sum = 0;
	uchar first, second;
	ushort shifted;
	uint seqNum = getSeqNum(addr);
	while( count > 1 )  {
		/*  This is the inner loop */
	   	first = *addr;
		*addr++;
		second = *addr;
		*addr++;
		shifted = (first | (second << 8)); 
		sum += shifted;
		count -= 2;
	}
	
	/*  Add left-over byte, if any */
	if( count > 0 ){
		sum += *addr;
	}
	/*  Fold 32-bit sum to 16 bits */
	while (sum>>16)
		sum = (sum & 0xffff) + (sum >> 16);
	if(sum == 0xFFFF){
		return true;
	} else {
		sem_wait(&printSemaphore);
		cout << "Packet " << seqNum << " checksum failed. Packet discarded." << endl;
		sem_post(&printSemaphore);
		return false;
	}
}
/**
  * isBOF - bool
  *		A method to check if the packet is the last packet or not.
  *		Will return true of it is BOF.
  * uchar* data - The Packet array to have the bit flipped.
  */
bool isBOF(uchar* data){
	bool result = false;
	if(data[6] == SIG_BOF){
		cout << "It is BOF." << endl;
		result = true;
	}
	return result;
}
/**
  * isEOF - bool
  *		A method to check if the packet is EOF.
  *		Will return true if EOF.
  * uchar* data - The Packet array to have the bit flipped.
  */
bool isEOF(uchar* data){
	bool result = false;
	if(data[6] == SIG_EOF){
		result = true;
	}
	return result;
}
/**
  * isPING - bool
  *		A method to see if the packet is a PING packet. This is needed
  *		on the sender to see if the result and end timers.
  * uchar* data - The packet array.
  */
bool isPING(uchar* data){
	bool result = false;
	if(data[6] == SIG_PING){
		result = true;
	}
	return result;
}
/**
  * sendPING - void
  *		A method to let the reciever know if the current packet is
  *		being used as a ping timer packet.
  * uchar* data - The packeet array to have the bit flipped.
  */
void sendPING(uchar* data){
	if (sendto(sockfd, data, sizeof(data), 0, (struct sockaddr *)&their_addr, addr_len) == -1){
		perror("Receiver: sendto");
		exit(1);
	} else {
		free(data);
	}
}

/**
  * printdata - void
  *		Prints the data
  * uchar* data - the packet array.
  */
void printdata(uchar* data, int count){
	cout << " | Data: ";
	for(int i = 0; i < count;i++){
		printf("%X ",data[i]);
	}
	cout << " |" << endl;
}

/**
  * getChecksum - ushort
  * 	Checks the array for the checksum value.
  *		Returns the checksum value as is from the array.
  * uchar* data - the acket array.
  */
ushort getChecksum(uchar* data){
	ushort cksum = (data[5] << 8) + data[4];
	return cksum;
}

/**
  * clearChecksum - void
  * 	Clears the checksum so that the comparative checksum
  *		doesn't screw up the calculation on this end.
  * uchar* addr - the data array.
  */
void clearChecksum(uchar* addr){
	addr[4] = 0;
	addr[5] = 0;
}

/**
  * sendAck - void
  *		Sends an ack to the sender, after confirming that the checksum
  *		value was good. This will allow the sender then to progress to
  * 	the next packet.
  * ushort sequenceNumber - The sequence number of the packet that needs
  * 				to be acknolwedged.
  */
void sendAck(uchar* ack, uint sequenceNumber){
	setSeqNum(ack, sequenceNumber);
	ack[5] = 0x06;	//ACK ascii code.
	if ((sendto(sockfd, ack, 5, 0, (struct sockaddr *)&their_addr, addr_len)) == -1){
		perror("Receiver: sendto");
		exit(1);
	}
}
/**
  * getSeqNum - uint
  *		Retrieves the sequence number from the header of the array. This 
  *		is essentially the reverse of the above function, and will pull
  *		4 one byte chars out, and add them together.
  * uchar* data - The packet, with all information.
  */
uint getSeqNum(uchar* data){
	uint seq = (data[3] << 24) + (data[2] << 16) + (data[1] << 8) + data[0];
	return seq;
}

/**
  * setSequesnceNumber - void
  * 	Sets up the header of the packet array to include the sequence number.
  *		Since we wanted the sequence number we wanted to support an unsigned int,
  * 	We needed 4 char to cover the range of int, since an int is typically 4 
  *		bytes, and a char is 1 byte.
  *	uchar* data - The packet, with all information
  * uint sequenceNumber - The number that will be inserted into the array header.
  */
void setSeqNum(uchar* packet, uint sequenceNumber){
	packet[0] = (sequenceNumber & 0x000000FF);
	packet[1] = ((sequenceNumber & 0x0000FF00) >> 8);
	packet[2] = ((sequenceNumber & 0x00FF0000) >> 16);
	packet[3] = ((sequenceNumber & 0xFF000000) >> 24);
}

// get sockaddr, IPv4 or IPv6:
void *get_in_addr(struct sockaddr *sa) {
	if (sa->sa_family == AF_INET) {
		return &(((struct sockaddr_in*)sa)->sin_addr);
	}
	return &(((struct sockaddr_in6*)sa)->sin6_addr);
}

/** bindSocket - int
  * 	Sets up the socket to use the unreliable data transfer, rather than the 
  *		normal sockets.
  *
  * string hostName - The hostname that we wish to send our data to.
  */
int bindSocket(){
	cout << "Binding socket..." << endl;
	memset(&hints, 0, sizeof hints);
	hints.ai_family = AF_UNSPEC; // set to AF_INET to force IPv4
	hints.ai_socktype = SOCK_DGRAM;
	hints.ai_flags = AI_PASSIVE; // use my IP
	
	if ((rv = getaddrinfo(NULL, SERVERPORT, &hints, &servinfo)) != 0) {
		fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
		return 1;
	}
	
	// loop through all the results and bind to the first we can
	for(p = servinfo; p != NULL; p = p->ai_next) {
		if ((sockfd = socket(p->ai_family, p->ai_socktype,
				p->ai_protocol)) == -1) {
			perror("listener: socket");
			continue;
		}
		
		if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
			close(sockfd);
			perror("listener: bind");
			continue;
		}
		
		break;
	}
	
	if (p == NULL) {
		fprintf(stderr, "listener: failed to bind socket\n");
		return 2;
	}
	
	freeaddrinfo(servinfo);
	cout << "Socket bound." << endl;
	return 0;
}


int main(void) {
	string acksLostStr;
	string windowSize;
	string maxSeqNum;
	cout << "Set receiver's window size: ";
	getline(cin, windowSize);
	cout << "Range of sequence numbers: ";
	getline(cin, maxSeqNum);
	maxSequenceNumber = atoi(maxSeqNum.c_str());
	cout << "Acks you would like lost: (R for random)";
	getline(cin, acksLostStr);
	RWS = atoi(windowSize.c_str());
	//RWS = 1;
	//acksLostStr = "45 89 104 1000";
	suppressOutput = false;
	uint acksLostInt;

	
	if (acksLostStr=="r"||acksLostStr=="R"){
		randomAcks= true;
		acksLostStr ="";
	}
		istringstream acksLostSS(acksLostStr);
		while(acksLostSS >> acksLostInt){
			acksLost.push(acksLostInt);
		
	}

	
	init();
	return 0;
};

